/*
 * Miccsnookersim, a snooker simulation with AI
 * Copyright (C) 2009 Enno Ruijters, Frans van den Heuvel, Yannick Thimister,
 * Ali B. Ozmen, Esther Verhoef, Achim Leydecker
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import java.awt.Color;
/**
 * Class representing a snooker ball
 */

public class Ball implements Comparable<Ball>
{
	private Vector position;
	private Vector velocity;
	private Vector angularVelocity;
	private Color color;
	private int value;
	public static final double RADIUS = 2.625;
	private static final double SLIDING_FRICTION_DELTA_V = 50;
	private static final double ROLLING_FRICTION_DELTA_V = 150;
	private static final double SPINNING_FRICTION_DELTA_V = 10;
	private static final double SIDESPIN_FUDGE_FACTOR = 500;

	private static final Vector XY_PLANE = new Vector(1,1,0);
	private static final Vector ROTATION_CORRECTION = new Vector(1,-1,1);
	/* Corrects for the inverted y-axis in the rotation */

	public int compareTo(Ball b)
	{
		if (value > b.value)
			return 1;
		if (value < b.value)
			return -1;
		return 0;
	}

	public Ball(Vector position, Color color, int value)
	{
		this.position = position.clone();
		this.velocity = new Vector(0, 0, 0);
		this.angularVelocity = new Vector(0, 0, 0);
		this.color = color;
		this.value = value;
	}

	public Ball clone()
	{
		Ball ret = new Ball(position, color, value);
		ret.velocity = velocity.clone();
		ret.angularVelocity = angularVelocity.clone();
		return ret;
	}

	public synchronized void advanceTime(double time, Table t,
	                                     boolean withNoise)
	{
		double frictionCorrection = 0;
		if (withNoise) {
			frictionCorrection = t.getFrictionModifier(
					position.getElements()[0],
					position.getElements()[1]);
		}
		double sliding_friction = SLIDING_FRICTION_DELTA_V + frictionCorrection;
		double rolling_friction = ROLLING_FRICTION_DELTA_V + frictionCorrection;
		double spinning_friction = SPINNING_FRICTION_DELTA_V + frictionCorrection;
		Vector deltaPosition = velocity.clone();
		deltaPosition.scale_(time); /* Motion of center of mass */
		position = position.add_(deltaPosition);

		angularVelocity.mul_(ROTATION_CORRECTION);
		double extraDeltaX = angularVelocity.getElements()[1] * RADIUS;
		double extraDeltaY = angularVelocity.getElements()[0] * RADIUS;
		Vector rollingV = new Vector(extraDeltaX, extraDeltaY, 0);
		rollingV = rollingV.mul_(XY_PLANE);
		Vector slidingV = Vector.subtract(velocity, rollingV);
		slidingV = slidingV.mul_(XY_PLANE);

		if (slidingV.magnitude() > sliding_friction * time) {
			slidingV = slidingV.normalize_();
			slidingV = slidingV.scaleTo_(sliding_friction);
			slidingV = slidingV.scale_(time);
		}
		velocity = velocity.sub_(slidingV);
		slidingV = slidingV.scale_(1/RADIUS);
		slidingV = slidingV.mirrorY_().rotate_(Math.PI * -0.5);
		angularVelocity = angularVelocity.sub_(slidingV);

		rollingV = angularVelocity.clone().mul_(XY_PLANE);
		if (rollingV.magnitude() > rolling_friction * time) {
			rollingV = rollingV.scaleTo_(rolling_friction);
			rollingV = rollingV.scale_(time);
		}
		angularVelocity = angularVelocity.sub_(rollingV);

		angularVelocity.mul_(ROTATION_CORRECTION);

		double sideSpin = angularVelocity.getElements()[2] * time;
		velocity.rotate_(-sideSpin / SIDESPIN_FUDGE_FACTOR);
		angularVelocity.rotate_(-sideSpin / SIDESPIN_FUDGE_FACTOR);

		double sideSpinFriction = angularVelocity.getElements()[2];
		if (sideSpinFriction > spinning_friction * time)
			sideSpinFriction = spinning_friction * time;
		if (sideSpinFriction < -spinning_friction * time)
			sideSpinFriction = -spinning_friction * time;
		angularVelocity.sub_(new Vector(0,0,sideSpinFriction));
	}

	public Vector getPosition()
	{
		return position;
	}

	public Color getColor()
	{
		return color;
	}

	public int getValue()
	{
		return value;
	}

	public Vector getVelocity()
	{
		return velocity;
	}

	public void setVelocity(Vector newVelocity)
	{
		velocity = newVelocity;
	}

	public void setPosition(Vector newPosition)
	{
		position = newPosition.clone();
	}

	public double getRadius()
	{
		return RADIUS;
	}

	public void setAngularVelocity(Vector newAV)
	{
		angularVelocity = newAV;
	}

	public Vector getAngularVelocity()
	{
		return angularVelocity;
	}

	public boolean isStationary()
	{
		double[] tmp;
		if (velocity.magnitude() != 0)
			return false;
		tmp = angularVelocity.getElements();
		if (tmp[0] != 0 || tmp[1] != 0)
			return false;
		angularVelocity = angularVelocity.sub_(angularVelocity);
		return true;
	}

	public double distanceFrom(Ball other)
	{
		return position.distanceFrom(other.position);
	}
}
